package com.liuguang.gateway.config.security.filter;

import com.alibaba.fastjson.JSONObject;
import com.liuguang.api.redis.RedisService;
import com.liuguang.common.enums.CommonResultEnum;
import com.liuguang.common.enums.RedisPreFixEnum;
import com.liuguang.common.pojo.CommonResult;
import com.liuguang.gateway.config.security.dao.UserDOMapper;
import com.liuguang.gateway.config.security.others.CurrentUserHolder;
import com.liuguang.common.utils.JwtUtil;
import com.liuguang.common.utils.WebUtil;
import com.liuguang.api.gateway.security.pojo.bo.AuthUser;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * @ClassName: JwtFilter
 * @Description： 添加在 UsernamePasswordAuthenticationFilter 前进行权限验证
 * @Author: wanghr
 * Date: 4:52 PM 2020/9/11
 * Version: 1.0
 */
@Component
public class JwtFilter extends OncePerRequestFilter {
    private static final Logger logger = LoggerFactory.getLogger(JwtFilter.class);

    @DubboReference
    private RedisService redisService;

    @Autowired
    private UserDOMapper userDoMapper;

    //这部分的内容在之前的网关已经进行过处理，直接通过就行
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//        String token = null;
//        String uri = request.getRequestURI();
//        logger.info("请求路径：{}", uri);
//        String tokenInHeader = request.getHeader("token");
//        String tokenInParameter = request.getParameter("token");
//        if (StringUtils.isEmpty(tokenInHeader)) {
//            token = tokenInParameter;
//        } else {
//            token = tokenInHeader;
//        }
//
//        if (StringUtils.isNotEmpty(token)) {
//            if (JwtUtil.validateTokenWithKey(token,JwtUtil.getJwtClaimKey())) {
//                //验证成功，解析和处理
//                parseAndProcess(response, token);
//            } else {
//                // token 验证失败如何返回给前端 提示信息
//                //关于解析失败的处理：
//                // 1、直接打印日志向下继续执行：后面会在权限验证的时候抛出异常，被ExceptionTranslationFilter捕获，
//                //进入到自己配置的 AuthenticationEntryPoint 中 使用response.getWriter().write() 方法返回友好的提示
//
//                // 2、在打印日志后，response.sendError后return；这时候会在上层的进行捕获，并请求转发 /error 请求，再次走流程
//                //这时候实现了 OncePerRequestFilter 过滤器的不在执行，实际上最终还是会到方法一验证时候抛出异常，再处理
//                //此种处理方式：前端显示的友好信息，不是此处添加的返回信息，而是在AuthenticationEntryPoint 配置的提示信息
//
//                //3、打印日志，直接在这步进行 response.getWriter().write() 省去了后序的执行流程。
//
//                // 方法一：
//                logger.info("解析失败");
//
//                //方法二：
////                response.sendError(403,"z这种形式的解析异常返回");
////                return;
//
//
//                //方法三：
////                response.setStatus(200);
////                response.setCharacterEncoding("UTF-8");
////                response.setContentType("application/json; charset=utf-8");
////                Map<String, Object> result = new HashMap<>();
////                result.put("code", 403);
////                result.put("data", "token无效或过期。");
////                response.getWriter().write(JSONObject.toJSONString(result));
//
//                WebUtil.responseFriendly(response, 403, "token无效或过期。");
//                return;
//
//                // token 过期后，前端需要重新的获取token，那么这个token信息如何生成？ 让用户重新登录
////                ExceptionTranslationFilter
//
//                //todo:这是在有SpringSecurity 框架的时候的执行流程，那么在没有安全框架的情况下，直接sendError 可以友好提示吗?
//                //可以
//            }
//        }
        filterChain.doFilter(request, response);
    }

    private void parseAndProcess(HttpServletResponse response, String token) throws IOException {
        logger.info("token获取成功并可以解析，开始解析token");
        Map parseToken = JwtUtil.parseTokenWithKey(token,JwtUtil.getJwtClaimKey());
        logger.info("解析后token：{}", parseToken);

        Date expiredTime = (Date) parseToken.get("expiredTime");
        System.out.println("expiredTime = " + expiredTime);

        List<String> list = (List<String>) parseToken.get("auth");
        String username = (String) parseToken.get("username");
        List<GrantedAuthority> authorityList = new ArrayList<>();
        list.forEach(s -> authorityList.add(new SimpleGrantedAuthority(s)));
        //向 SecurityContextHolder 添加 authentication 信息，因包含权限信息，后面不再进行认证过程，只进行授权
        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(username, "", authorityList);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        long time = expiredTime.getTime() - System.currentTimeMillis();
        System.out.println("time = " + time);
        if (time < 300000) {
            //未过期距离过期时间是否小于5分钟，小于该时间刷新token
            String newToken = JwtUtil.createToken(parseToken);
            //将新token 放到 response 中,
            // 这就意味着前端每次调用接口后都要更新一下保存的token信息？这样也太麻烦了
            response.addHeader("token", newToken);
        }

        // redis 保存用户信息，方便其他微服务获取用户信息，此处主要是检查作用，因为用户保存时间为1天
        //检查：在redis 获取用户信息，获取不到数据库获取添加到redis
        String key = RedisPreFixEnum.SAVE_USER_INFO.getValue() + username;
        CommonResult<String> redisResult = redisService.get(key);
        if (!CommonResultEnum.SUCCESS.getCode().equals(redisResult.getCode())) {
            //在redis获取不到，数据库获取
            logger.info("在redis未获取到用户：{}信息，数据库获取",username);
            List<AuthUser> authUserList = userDoMapper.selectByUsername(username);
            int authUserListSize = authUserList.size();
            if (authUserListSize != 1) {
                logger.info("根据用户名查询不到用户，或查询出多个用户");
                WebUtil.responseFriendly(response, 500, "根据用户名查询不到用户，或查询出多个用户");
            }
            AuthUser authUser = authUserList.get(0);
            String userInfoKey = RedisPreFixEnum.SAVE_USER_INFO.getValue() + username;
            String userInfo = JSONObject.toJSONString(authUser);
            redisService.setWithTime(userInfoKey,userInfo,24*60L);

        }
        AuthUser currentUser = CurrentUserHolder.getCurrentUser();
        if (ObjectUtils.isEmpty(currentUser)) {
            logger.info("未在CurrentUserHolder获取用户信息");
            AuthUser authUser = JSONObject.parseObject(redisResult.getData(), AuthUser.class);
            logger.info("在redis 中获取userinfo：{}",authUser);
            CurrentUserHolder.setCurrentUser(authUser);
        }
    }
}
